當我們在評估新專案中 bundler 的選型,或有年代感的複雜大型專案的 bundler 遷移時,用一個簡單基本的專案來實驗其中有疑慮或想確認的設定都是最有效率的。以之前我想實驗 tree-shaking 設定為例,因為實驗設定時需要反覆嘗試重新打包,要是打包一次要五分鐘以上,那咖啡可能要泡很多杯。
因此用小專案做實驗,一方面能夠以好理解的方式學習,另一方面可以快速打包來 try & error。
講回正題,延續昨天的實驗專案程式碼,今天就來嘗試看看 Rspack 走到打包時可以做些什麼設定與利用什麼工具來達到最佳化。
雖然昨天有提過,但這篇在簡單複習一下,以下這篇中雖然專案本身是 Rsbuild,但其底層的 bundler 是 Rspack,兩者在打包的設定上算是互相能參考但 Rsbuild 有做一些客製化而有所不同,關於 Rspack 有不同的設定方式會附在每個小節最下方,但這篇文章介紹時會以 Rsbuild
為主。
另外這篇文章的實驗以 macOS 執行,看文件有些地方提到 Windows 系統在部份設定上會需要使用 corss-env,有需要的讀者可以再另外參考文件。
可以直接執行以下指令,這個是目前專案中 rsbuild build
的 alias:
$ pnpm run build
執行後可以看到能正確將打包的檔案放到 dist
資料夾下方:
> rsbuild build
info Type checker is enabled. It may take some time.
● web ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) emitting after emit
File Size Gzipped
dist/static/css/index.43c217db.css 0.37 kB 0.26 kB
dist/index.html 0.37 kB 0.25 kB
dist/static/js/index.ccf11837.js 1.6 kB 0.86 kB
dist/static/js/lib-react.c79a76de.js 140.2 kB 45.0 kB
Total size: 142.5 kB
Gzipped size: 46.4 kB
如果你曾經嘗試在使用 Webpack 做為 bundler 的專案上分析過安裝套件的大小分佈,應該會看過上面這張圖,這個正是 webpack-bundle-analyzer 這個 plugin 幫忙做的工作,它會在打包後輸出一份如上圖的 HTML 報告檔,可供開發者分析是否有能夠減少的打包成本。
而身為 Webpack 兼容高手的 Rsbuild,裡面也有內建引入,但預設在打包時是關閉的,如果需要啟用的話參考文件可在設定中加入這段來開啟:
// rsbuild.config.ts
const environment = process.env.NODE_ENV;
const isDev = environment === 'development';
export default defineConfig({
performance: {
bundleAnalyze: {
// 輸出成靜態 HTML 檔案
analyzerMode: isDev ? 'disabled' : 'static',
// 輸出報告檔名
openAnalyzer: !isDev,
// 輸出報告檔名
reportFilename: `report-${environment}.html`,
},
},
});
這裡如果沒有另外根據 development
調整的話,會在 pnpm run dev
時也開啟,但一般來說習慣上打包分析會想在 production mode 才看。調整設定如上後,再試著執行 pnpm run build
,應該會自動開啟網頁顯示如下圖:
這裡因為第三方套件目前只有用上 React,所以看不出像上面影片那麼密密麻麻,隨著之後套件越裝越多且有在 source code 中用上就會顯示在這裡。如果想實驗看看的話可以試試 pnpm add moment lodash
安裝幾個套件後,嘗試在 App.tsx
隨意引入重 build 應該可以看到如下的效果:
💡 如果是 Rspack 的話內建也有裝,只是開啟的部份更單純,只要使用
rspack build —analyze
指令就可以 (ref)
上面講了以前在 Webpack 常使用的打包分析工具,就不能不提提其實在 Rspack 生態系中也有另外實作一個稱為 Rsdoctor
的工具專門拿來做視覺化的構建分析。這裡也參考文件來試玩一下:
$ pnpm add @rsdoctor/rspack-plugin -D
可以將以下 script 加到 package.json
中,因為文件上有警告目前此工具因為還在開發中,不建議被使用在 production build 中,因此先加在 dev
環境來測試:
{
"scripts": {
"dev:rsdoctor": "RSDOCTOR=true rsbuild dev",
}
}
試著執行看看指令:
$ pnpm run dev:rsdoctor
... 略 ...
info Rsdoctor analyze server running on: http://localhost:8791/index.html
執行後會看到上面會自動 serve 起一個 Rsdoctor 分析頁面的網址,打開後應該能看到以下畫面:
可以看到其中有提供許多方便分析打包過程的資訊,像是每個 loader 與 plugin 執行花費時間、bundle size、打包警告與錯誤建議等等,詳細全部功能可以參考文件。
有個好奇不知道它有沒有強大到可以建議我替換更好的套件,因此來實驗裝裝看這些比較 legacy 的套件:
$ pnpm add moment lodash
$ pnpm add -D @types/lodash
💡 一般來說在現代網頁開發時,會用
date-fns
或day.js
來取代moment
,而使用lodash-es
來取代 CJS 版本的lodash
,都是為了讓 bundle size 更減小,網頁載入速度更快。
因為這個專案是使用 TypeScript,所以也裝一下 @types/lodash
型別才不會報錯。再調整 src/App.tsx
如下將套件引入:
import STYLES from './App.module.scss';
import type { Author } from './types';
import _ from 'lodash';
import moment from 'moment';
const App = () => {
const author: Author = {
name: 'codefarmer',
email: 'codefarmer.tw@gmail.com',
url: 'https://codefarmer.tw',
};
return (
<div className={STYLES.content}>
<h1>Rsbuild with React</h1>
<p>Start building amazing things with Rsbuild.</p>
<p>Author: {author.name}</p>
<p>lodash/has: {_.has({ a: 1 }, 'a') ? 'true' : 'false'}</p>
<p>moment: {moment().format('YYYY-MM-DD HH:mm:ss')}</p>
</div>
);
};
重新執行 pnpm run dev:rsdoctor
後再看看警告與 bundle size 頁面,似乎沒有做任何提示:
因為原本印象中有看到文件有提到,結果發現是我之前看文件這段時會錯意,它只是建議可以用這些套件分析工具來確認套件大小,例如可以試著將 moment
換成 day.js
這些優化方法,並不是代表 Rsdoctor 可以幫忙偵測。
看文件其實在分析頁面中也是可以顯示 Webpack 分析工具的瓦片圖,這裡來實驗開啟看看,先在設定中加上這段:
// rsbuild.config.ts
...
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
const rsdoctorPlugin = new RsdoctorRspackPlugin({
supports: {
generateTileGraph: true,
}
});
export default defineConfig({
tools: {
rspack: {
plugins: [rsdoctorPlugin],
},
},
});
以上都調整完成後再實際執行一次 pnpm run dev:rsdoctor
試試應該可以看到如下圖的效果:
前面在《esbuild 是什麼?code splitting 是什麼?》這篇時有提到 esbuild 在 code splitting 的方面做的不完全,也因此 Rspack 團隊才決定重新打造這個工具。
因此如果看到文件關於將程式碼切分為小塊的這段說明中,會看到 Rsbuild 中甚至直接抽成一個特製的 performance.chunkSplit
設定,提供各種切塊策略直接根據需求開箱即用:
split-by-experience
:自動將常用的 npm 套件拆為檔案大小適中的 chunksplit-by-module
:每個 npm 套件對應一個 chunk,此方式有提到可能在沒使用 HTTP/2 時造成 request waterfall 的問題split-by-size
:另根據 minSize
與 maxSize
依照檔案大小切 chunkall-in-one
:全部都打包到同一個 chunk 中single-vendor
:將所有第三方套件包在同一個 chunk 中custom
:自訂設定似乎多少能減少像學習 Webpack 中的 optimization.splitChunks
那樣複雜的設定的心智負擔。
以下來實驗其中 3 種試試。
// rsbuild.config.ts
export default defineConfig({
performance: {
chunkSplit: {
strategy: 'split-by-experience',
}
}
});
// rsbuild.config.ts
export default defineConfig({
performance: {
chunkSplit: {
strategy: 'split-by-module',
},
},
});
// rsbuild.config.ts
export default defineConfig({
performance: {
chunkSplit: {
strategy: 'split-by-size',
maxSize: 50000,
minSize: 10000,
},
},
});
可以看到不同的切塊策略切出來的顆粒度也不同,可以依據自己的需求與使用環境決定能在權衡之後採取什麼切法。而也在使用這樣的套件分析工具後,我們可以更視覺化地看出在這個例子中故意去引入的兩個大套件 moment
與 lodash
都是可被優化去改用 dayjs
與 lodash-es
來減少 bundle size。
💡 Rspack 關於 code splitting 的部份也有說明,看起來連文件都以 CC BY 4.0 參考 Webpack 文件,應該是目標在完全兼容。
今天利用昨天的 Rsbuild 入門專案實際測試了幾個打包設定、打包分析工具,試著引入 Rspack 生態系中的 Rsdoctor 來幫忙分析打包過程中的狀況,其中用上了許多圖表與視覺化資訊,比起過往 Webpack 生態系中單純只用 webpack-bundle-analyzer
看,可以感覺到現代工具真的是進步許多。
另外也看了前面 Rspack 生態號稱比 esbuild 厲害的 code splitting 特性,提供多個開箱即用的切塊策略,目前初步使用起來的開發體驗非常順暢,文件看起來沒有難讀難設定的,只是有許多部份如果要能與 Webpack 完全兼容應該還會需要再等等。
希望這兩篇的 Rspack / Rsbuild / Rsdoctor 入門文件導讀與實驗能讓在觀望的讀者們一點幫助,如果有看不懂或有寫錯的地方再麻煩留言告訴我,感謝你的閱讀,我們明天見!